using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using CustomCMS.API.Util;
using System.Configuration;
using System.Xml;
using log4net;
using CustomCMS.API.Wrapper;

namespace CustomCMS.API.Persistance
{
	public abstract class BasePersistanceProvider
    {
		private static readonly ILog _log = LogManager.GetLogger(typeof(BasePersistanceProvider));
		
		private ConfigurationHandler _config = null;

        internal string RepositoryLocation
        {
            get { return GetConfig().Repository; }
        }

		internal Dictionary<string, ObjectModel.User> Users
		{
			get { return GetConfig().Users; }
		}

        private ConfigurationHandler GetConfig()
        {
            if( _config == null )
                _config = (ConfigurationHandler)ConfigurationManager.GetSection("CustomCMS");

            return _config;
        }
		
		/// <summary>
		/// Takes the content in it's xml form and convert it into key/value list
		/// </summary>
		/// <param name="xmlContent"></param>
		/// <returns></returns>
		protected Dictionary<string, string> GetContentValues(string xmlContentFilename)
		{
			string text = FileSystemWrapper.GetFileContent(xmlContentFilename);
			XmlDocument content = new XmlDocument();
			content.LoadXml(text);
            
			XmlNodeList valList = content.SelectNodes("root/param");

			Dictionary<string, string> vals = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);

			foreach (XmlNode node in valList)
				vals[node.Attributes["name"].Value] = node.InnerText;

			return vals;
		}

		/// <summary>
		/// Retrieves all specific cms tags, along with up to 1 additional tag parameter.
		/// </summary>
		/// <param name="template">template content</param>
		/// <param name="name">name of the cms tag</param>
		/// <param name="attribute">additional attribute to retrieve</param>
		/// <param name="attrDefault">default value of attribute</param>
		/// <returns></returns>
		protected Dictionary<string, string[]> GetTemplateKeys(string template, string name, string attribute, string attrDefault)
		{
			Dictionary<string, string[]> keys = new Dictionary<string, string[]>(StringComparer.InvariantCultureIgnoreCase);
            GetTemplateKeys(template, name, attribute, attrDefault, keys);
			return keys;
		}

        protected void GetTemplateKeys(string template, string name, string attribute, string attrDefault, Dictionary<string, string[]> keys)
        {
            MatchCollection matches = Regex.Matches(template, "<cms:" + name + ".*?(?:</cms:" + name + ">|\\/>)", RegexOptions.IgnoreCase);

            foreach (Match match in matches)
            {
                string key = Regex.Replace(match.Value, ".*name=\"(.*?)\".*", "$1", RegexOptions.IgnoreCase);

                string attrVal = attrDefault;

                if (Regex.IsMatch(match.Value, ".*" + attribute + "=\".*?\".*"))
                    attrVal = Regex.Replace(match.Value, ".*" + attribute + "=\"(.*?)\".*", "$1", RegexOptions.IgnoreCase);

                attrVal = attrVal == string.Empty ? attrDefault : attrVal;

                keys[key] = new string[] { match.Value, attrVal };
            }
        }

		protected string GetPageContent(string templateFile, string contentFilename, Dictionary<string, int> states, out List<string> missingTemplates)
		{
            missingTemplates = new List<string>();
            StringBuilder template = new StringBuilder(CustomCMS.API.ObjectModel.Page.DEFAULT_PAGE_HEADER);
            try
            {
                template.Append(((IPersistanceProvider)this).GetTemplate(templateFile, "*").Content);
            }
            catch (Exception)
            {
                missingTemplates.Add(templateFile);
            }

			//replacing variables with their values - must come first to support variable includes.
			Dictionary<string, string> content = GetContentValues(contentFilename);
			Dictionary<string, string[]> templateVars = GetTemplateKeys(template.ToString(), "variable", "values", "");

			ReplaceVariables(template, templateVars, content);

			//insert sub templates
			IncludeInnerTemplates(template, templateVars, content, missingTemplates);

            templateVars = GetTemplateKeys(template.ToString(), "variable", "values", "");
            Dictionary<string, string[]> templateKeys = GetTemplateKeys(template.ToString(), "content", "section", "General");
            Dictionary<string, string> sectionNames = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
            Dictionary<string, int> sectionStates = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
            List<string> empty = new List<string>();
            foreach (KeyValuePair<string, string[]> p in templateKeys) 
            {
                string s = p.Value[1];
                int c = s[0] == '-'? 2 : s[0] == '+'? 1 : 0;
                if (c != 0) templateKeys[p.Key][1] = s = s.Substring(1);
                if (sectionNames.ContainsKey(s)) s = sectionNames[s]; else sectionNames[s] = s;
                if (!sectionStates.ContainsKey(s)) sectionStates[s] = c; else sectionStates[s] |= c;
                if (Regex.IsMatch(p.Key, "^\\s*$")) empty.Add(p.Key);
            }
            if (!sectionNames.ContainsKey("VARIABLES")) sectionNames["VARIABLES"] = "VARIABLES";
            foreach (string e in empty) templateKeys.Remove(e);
            foreach (string s in states.Keys) if (sectionStates.ContainsKey(s) && sectionStates[s] != 0) sectionStates[s] = states[s];

			StringBuilder result = new StringBuilder();
			result.Append("<root>");
			result.AppendFormat("<template>{0}</template>", templateFile);

			//append variables
			Dictionary<string, string[]>.Enumerator ptr = templateVars.GetEnumerator();

			while (ptr.MoveNext())
			{
				string val = string.Empty;

				content.TryGetValue(ptr.Current.Key, out val);

                val = val == null ? string.Empty : val;

				//retrieve valid values list.
				string validValues = string.Empty;
				
				if (Regex.IsMatch(ptr.Current.Value[0], ".*values=\".*?\".*"))
					validValues = Regex.Replace(ptr.Current.Value[0], ".*(values=\".*?\").*", "$1", RegexOptions.IgnoreCase);

                result.AppendFormat("<param name=\"{0}\" section=\"{3}\" {1}>{2}</param>", ptr.Current.Key, validValues, val, sectionNames["VARIABLES"]);
			}

			Dictionary<string, List<string>> sortedParams = new Dictionary<string, List<string>>(StringComparer.InvariantCultureIgnoreCase);
            foreach (KeyValuePair<string, string[]> p in templateKeys)
            {
				string val = string.Empty;
				content.TryGetValue(p.Key, out val);
                val = val == null ? string.Empty : val;

                string s = sectionNames[p.Value[1]];
                string param = string.Format("<param name=\"{0}\" section=\"{1}\">{2}</param>", p.Key, s, val);

				List<string> temp;
                if (sortedParams.TryGetValue(s, out temp)) temp.Add(param);
                else sortedParams.Add(s, new List<string>(new string[] { param }));
			}
            foreach (List<string> l in sortedParams.Values) foreach (string p in l) result.Append(p);

			result.Append("</root>");

            result.Append("<state>");
            foreach (string s in sectionStates.Keys)
                result.AppendFormat("<section name=\"{0}\" state=\"{1}\"/>", s, sectionStates[s] == 0 ? 0 : (sectionStates[s] & 2) == 0 ? 1 : 2);
            result.Append("</state>");

			return result.ToString();
		}

		/// <summary>
		/// Process content merges a master template and a values page into one html result in 3 steps
		/// A. Embed inner templates
		/// B. Cleanup CMS comments
		/// C. install contnet values where CMS params are found.
		/// </summary>
		/// <param name="template">A string builder containing the master template</param>
		/// <param name="xmlContent"></param>
		/// <returns></returns>
		protected string ProcessContent(StringBuilder template, string contentFilename, out string warnings)
		{
			//save the list of variables
			Dictionary<string, string[]> templateVars = GetTemplateKeys(template.ToString(), "variable", "values", "");

			Dictionary<string, string> content = GetContentValues(contentFilename);

			//insert sub templates
            List<string> missing = new List<string>();
			IncludeInnerTemplates(template, templateVars, content, missing);
            StringBuilder w = new StringBuilder("");
            missing.ForEach(delegate(string m) { w.Append("\n\t"+m); });
            warnings = w.Length == 0 ? null : contentFilename + " is missing template" + (missing.Count == 1 ? ":" : "s:") + w.ToString();

			//remove template comments tags & variables tags
			RemoveTemplateComments(template);

			//install variable values
			ReplaceVariables(template, templateVars, content);

			//solve conditions
            new SolveIf(templateVars, content).Solve(template);
			SolveConditions(template);

			Dictionary<string, string[]> templateKeys = GetTemplateKeys(template.ToString(), "content", "section", "General");

			Dictionary<string, string[]>.Enumerator ptr = templateKeys.GetEnumerator();

            string t = template.ToString();
			while(ptr.MoveNext())
			{
				string val = string.Empty;
				content.TryGetValue(ptr.Current.Key, out val);
				val = val == null ? string.Empty : val.Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace("$", "$$");

                Regex r = new Regex(Regex.Replace(EscapeRE(ptr.Current.Value[0]), "section=(.).*[\"']([ \\t\\>/]?)", "section=$1.*?$1$2"), RegexOptions.IgnoreCase);
				t = r.Replace(t, val);
			}

			return t;
		}

		private static string EscapeRE(string s) 
		{
			return s == null ? "" : Regex.Replace(s, @"([\\^$?+*()\[\]{}.|])", "\\$1");
		}

		private void RemoveTemplateComments(StringBuilder template)
		{
			MatchCollection matches = Regex.Matches(template.ToString(), "<cms:comment.*?(?:</cms:comment>|\\/>)", RegexOptions.IgnoreCase);

			foreach (Match match in matches)
				template.Replace(match.Value, string.Empty);

			matches = Regex.Matches(template.ToString(), "<cms:variable.*?(?:</cms:variable>|\\/>)", RegexOptions.IgnoreCase);

			foreach (Match match in matches)
				template.Replace(match.Value, string.Empty);

		}

		private void IncludeInnerTemplates(StringBuilder template, Dictionary<string, string[]> templateVars, Dictionary<string, string> content, List<string> missingTemplates)
		{
			MatchCollection matches = Regex.Matches(template.ToString(), "<cms:include.*?(?:</cms:include>|\\/>)", RegexOptions.IgnoreCase);

			foreach (Match match in matches)
                IncludeInnerTemplate(template, match.Value, (IPersistanceProvider)this, templateVars, content, missingTemplates);
		}

		/// <summary>
		/// replacing variables with their values
		/// </summary>
		/// <param name="text">the content containing the variable place holders</param>
		/// <param name="templateVars">all variables available in current template</param>
		/// <param name="content">variable values</param>
		protected void ReplaceVariables(StringBuilder text, Dictionary<string, string[]> templateVars, Dictionary<string, string> varValues)
		{
			Dictionary<string, string[]>.Enumerator ptr = templateVars.GetEnumerator();

			while (ptr.MoveNext())
			{
				string val = string.Empty;

				varValues.TryGetValue(ptr.Current.Key, out val);

                val = val == null ? string.Empty : val.Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&");

				//if this is a valid values list and no value has been specified, we take the first value as default.
				if (String.IsNullOrEmpty(val) && ptr.Current.Value[1].Length > 0)
				{
					val = ptr.Current.Value[1].Split(new char[] { '|' })[0];
				}

				text.Replace("{" + ptr.Current.Key + "}", val);
			}
		}

 		private void SolveConditions(StringBuilder text)
		{
			MatchCollection matches = Regex.Matches(text.ToString(), "<cms:condition\\s+test='(.*?)'\\s+is_true='(.*?)'\\s+is_false='(.*?)'.*?(?:</cms:condition>|\\/>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);

			foreach (Match match in matches)
			{
				string result = string.Empty;

				string condition = Regex.Replace(match.Value, "<cms:condition\\s+test='(.*?)'\\s+is_true='(.*?)'\\s+is_false='(.*?)'.*?(?:</cms:condition>|\\/>)", "$1", RegexOptions.Singleline | RegexOptions.IgnoreCase);
				string isTrue = Regex.Replace(match.Value, "<cms:condition\\s+test='(.*?)'\\s+is_true='(.*?)'\\s+is_false='(.*?)'.*?(?:</cms:condition>|\\/>)", "$2", RegexOptions.Singleline | RegexOptions.IgnoreCase);
				string isFalse = Regex.Replace(match.Value, "<cms:condition\\s+test='(.*?)'\\s+is_true='(.*?)'\\s+is_false='(.*?)'.*?(?:</cms:condition>|\\/>)", "$3", RegexOptions.Singleline | RegexOptions.IgnoreCase);

				string[] sides = condition.Split(new char[] { '=' });

                if (sides[0].Length > 0 && sides[0][sides[0].Length - 1] == '!')
				{
					sides[0].Remove(sides[0].Length-1);
					result = sides[0] != sides[1] ? isTrue : isFalse;
				}
				else
					result = sides[0] == sides[1] ? isTrue : isFalse;

				text.Replace(match.Value, result);
			}
		}

		/// <summary>
		/// Recursively embed inner templates inside the master template.
		/// </summary>
		/// <param name="template">master template as a StringBuilder</param>
		/// <param name="include">the next include</param>
		private void IncludeInnerTemplate(StringBuilder template, string includeTag, IPersistanceProvider provider, Dictionary<string, string[]> templateVars, Dictionary<string, string> content, List<string> missingTemplates)
		{
			StringBuilder include = new StringBuilder(Regex.Replace(includeTag, ".*path=\"(.*)?\".*", "$1", RegexOptions.IgnoreCase));

			ReplaceVariables(include, templateVars, content);

			_log.Debug("inner template is loaded from file system which is not appropriate for the SVN provider");
            ObjectModel.Template subTemplate;
            try
            {
                subTemplate = provider.GetTemplate(include.ToString(), "");
            }
            catch (Exception)
            {
                missingTemplates.Add(Regex.Replace(include.ToString(), "\\*", "/"));
                template.Replace(includeTag, "");
                return;
            }

            StringBuilder sb = new StringBuilder(subTemplate.Content);
            GetTemplateKeys(sb.ToString(), "variable", "values", "", templateVars);

            MatchCollection matches = Regex.Matches(subTemplate.Content, "<cms:include.*?(?:</cms:include>|\\/>)", RegexOptions.IgnoreCase);
			foreach (Match match in matches)
				IncludeInnerTemplate(sb, match.Value, (IPersistanceProvider)this, templateVars, content, missingTemplates);

			template.Replace(includeTag, sb.ToString());
		}

		public bool IsActionAllowed(string action, string userId)
		{
			ObjectModel.User user = null;
			if (!GetConfig().Users.TryGetValue(userId, out user))
				throw new Exception("Unauthenticated user");

			return user.IsActionAllowed(action);
		}

		public void Authenticate(string userId, string password)
		{
			ObjectModel.User user = null;
			if (!GetConfig().Users.TryGetValue(userId, out user) || user.Password != password)
				throw new Exception("Unauthenticated user");
		}
    }
}
